Skip to content

Fix e2e on 11.12#560

Open
YogendraShelke wants to merge 9 commits into
mx/11.12.xfrom
update-screenshot-11.12
Open

Fix e2e on 11.12#560
YogendraShelke wants to merge 9 commits into
mx/11.12.xfrom
update-screenshot-11.12

Conversation

@YogendraShelke

@YogendraShelke YogendraShelke commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes three defects in IntroScreen's slide-position handling, all rooted in how SwipeableContainer reconciled activeIndex (React state) with the FlashList's real scroll offset. The previous code used an isInitializing flag flipped by a double requestAnimationFrame, which guessed at timing instead of tracking what was actually requested.

Problems Fixed

Problem 1

Reopening a page showed slide 1 instead of the slide in the Active slide attribute, and fired a spurious On slide change back to slide 1

Root cause

activeIndex initialized to 0 while initialScrollIndex rendered the attribute's slide, so state and screen disagreed on mount. The effect only re-scrolled when slide !== activeIndex, so a matching-but-wrong state was never

Fix

Seed activeIndex from the attribute via a useState initializer, and force the positioning scroll once on the first pass where width is known (hasAppliedInitialScroll), independent of whether activeIndex already matches


Problem 2

The initial slide was reached with an animation, briefly leaving slide content unreachable for TalkBack/VoiceOver

Root cause

goToSlide always animated. activeIndex applies immediately and drives accessibilityElementsHidden / importantForAccessibility, so the slide exposed to screen readers was off-screen until the animation landed

Fix

goToSlide(pageNum, animated = true); the initial jump passes animated: false


Problem 3

Using Next / Previous / pagination could snap back to slide 1 and report an extra slide change

Root cause

onMomentumScrollEnd fires for programmatic scrolls too, reporting the offset the list is leaving rather than the one it's heading to. That stale offset overwrote activeIndex and re-fired onSlideChange. A drag flag alone can't disambiguate: it's only cleared inside onMomentumScrollEnd, so a drag that produces no momentum event leaves it stuck true, and the next programmatic scroll's stale event is misread as a user swipe

Fix

Track pendingScrollTarget — the slide goToSlide actually requested. onMomentumScrollEnd ignores any event whose index disagrees with that target, clearing it once reached. onScrollBeginDrag clears the target so a user taking over is always honoured, which also prevents the target latching when a scroll emits no momentum event

@YogendraShelke
YogendraShelke requested a review from a team as a code owner July 23, 2026 07:23
@YogendraShelke
YogendraShelke force-pushed the update-screenshot-11.12 branch 2 times, most recently from 6eed515 to b6be27f Compare July 24, 2026 11:46
@YogendraShelke YogendraShelke changed the title chore: inrease maestro driver setup timeout Fix e2e on 11.12 Jul 27, 2026
@YogendraShelke
YogendraShelke force-pushed the update-screenshot-11.12 branch from c9a59ea to 1ac6b5e Compare July 27, 2026 12:14
@YogendraShelke
YogendraShelke force-pushed the update-screenshot-11.12 branch from 1ac6b5e to eda1e26 Compare July 27, 2026 12:14
YogendraShelke and others added 8 commits July 27, 2026 17:50
chore: fix tests

chore: update ios test timeout

chore: inrease maestro driver setup timeout

chore: address flaky tests

chore: fix tests

chore: revert

chore: revert
…et index

Flows navigate the Widgets menu by tapping a single letter, which filters and
re-renders the list, then immediately tap the widget name. Nothing waited for
that render, so the second tap raced it. Maestro's implicit element lookup
usually absorbed the gap, but on a loaded CI runner it did not:

  Tap on "B"... COMPLETED
  Tap on "Bottom Sheet"... FAILED
  Element not found: Text matching regex: Bottom Sheet

which then passed on retry with the same build. Three bottom-sheet flows hit
this in one run.

Insert an extendedWaitUntil on the widget name between the two taps, matching
the pattern Modal_basic_non_native.yaml already used for its "Open" button.
71 flows across 20 widgets carried the race; all are now guarded.

Verified: all 82 flow files still parse, all 71 index-taps are followed by a
wait, and no flow ended up with a duplicated consecutive wait.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
restart_simulator is the recovery path for a wedged Maestro/XCTest driver
("iOS driver not ready in time"), but it did not restart anything. It ran
`simctl shutdown all || true`, discarding the result, and prepare_ios.sh then
reported:

    Monitoring boot status for iPhone 17 Pro (...).
    Device already booted, nothing to do.

The device never went down, so the shard got an app reinstall on top of the
same wedged runtime and ran degraded from that point on. Seen in a run where
the smoke check lost its driver and a later assertion then timed out.

Changes:
- Kill the XCTest runner before shutting down, not after: while it holds the
  device, shutdown can fail.
- Target SIMULATOR_DEVICE_ID when known, falling back to `all`.
- Poll until the device reports Shutdown (bounded at 30s) and emit a
  ::warning:: if it never does, instead of silently continuing.
- Drop the blind `sleep 10`. Targeted `simctl shutdown <udid>` is synchronous
  (measured ~3s to reach Shutdown), so the sleep only padded every retry.

Verified against a real simulator: booted device reaches Shutdown in ~3.4s
(was ~13s); an already-shutdown device logs the benign CoreSimulator 405 and
continues; the unset-UDID path falls back to `all`. Both return exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…creenshots

20 flows waited before takeScreenshot by watching for text that by design never
appears, with optional: true:

    - extendedWaitUntil:
         visible: randText  # Any random text that does not exist in the UI
         optional: true
         timeout: 15000

That is a sleep, not a wait. It ALWAYS burns the full timeout (it can never
succeed early) and never confirms the UI settled, so it is both slow and flaky.
It cost 325s (5.4 min) per full run PER PLATFORM, and still was not enough: a
bottom-sheet screenshot came in at 14.7% similarity against a 95% threshold
because the sheet was mid-expand when captured, then passed on retry.

Replace with waitForAnimationToEnd, which polls until the screen stops changing
-- returning as soon as the image decode / sheet animation finishes, and still
capping at the timeout. Already used by animation-native, so no new concept.

Each site keeps its existing timeout as the cap, so no flow gets a smaller
budget than before; the saving comes from returning early rather than from
lowering the ceiling.

Verified: no randText remains anywhere, all 82 flow files parse, every
waitForAnimationToEnd has a timeout, all 20 timeouts carried over unchanged,
and the assertVisible count is unchanged (99) so no real assertion was dropped
along with the stale comments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NativePipeline already had concurrency + cancel-in-progress, but Build,
UnitTests, Release and ShaCheck had none — so every push to a PR left the
previous run of each still going. On GitHub Free all of them share one
20-concurrent-job cap with NativePipeline's ~10 test shards, so stale runs
were not just noise: they delayed the run for the commit under review.

cancel-in-progress is gated on pull_request. A push run on main / mx-* is the
recorded CI result for that commit (and Release.yml's push path publishes),
so those are allowed to finish; only superseded PR runs get cancelled.

Not added to MarketplaceRelease.yml (tag-triggered publish — never cancel),
NightlyDispatcher.yml (scheduled), the workflow_dispatch-only workflows, or
PullRequestLabeler.yml (seconds long).

fix: make native tests cancellable

ci: make cancelling the native pipeline actually stop it

Clicking "Cancel workflow" appeared to do nothing for minutes. The reason is
that `if: always()` is exempt from cancellation by design: a step or job with
always() keeps running (and a job with it can even start) after the run is
cancelled. The pipeline used always() in eleven places, including every upload
inside archive-test-results — so on cancel each of the ~10 in-flight shards
first uploaded five artifact sets nobody was waiting for.

Replaced all of them with `!cancelled()`, which is equivalent for the cases we
care about — it is true on success AND on failure, so failing shards still
archive their screenshots/logs/videos/diffs, and jobs with skipped
dependencies still run — but is false on cancellation. A shard that trips
timeout-minutes is not "cancelled" in this sense, so timeout artifacts survive.

Not fixed here: the maestro bash harness does not trap SIGINT/SIGTERM, so a
shard mid-flow still runs to the runner's force-kill. Cancellation is much
faster, not instant.
@YogendraShelke
YogendraShelke force-pushed the update-screenshot-11.12 branch from 062e66f to 238a920 Compare July 27, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants